
package com.haiyou.common.util.random;

import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.lang3.RandomUtils;

import com.google.common.collect.Lists;

import lombok.extern.slf4j.Slf4j;

/**
 * 
 * 
 * @Description: 按概率随机
 * @author xingyuan
 * @date 2022年7月12日 下午6:04:30
 */
@Slf4j
public class ProbabilityUtils {

	/** 概率接口,默认实现了比较器（权重从大到小） */
	public interface Probability extends Comparable<Probability> {
		/** 获取权重 */
		int getWeight();

		@Override
		default int compareTo(Probability o) {
			return o.getWeight() - this.getWeight();
		}
	}

	public static <T extends Probability> int getSum(List<T> list) {
		return list.stream().mapToInt(T::getWeight).sum();// 和
	}

	public static <T extends Probability> int getMax(List<T> list) {
		return list.stream().mapToInt(T::getWeight).max().getAsInt();// 最大
	}

	public static <T extends Probability> int getMin(List<T> list) {
		return list.stream().mapToInt(T::getWeight).min().getAsInt();// 最小
	}

	public static <T extends Probability> double getAverage(List<T> list) {
		return list.stream().mapToDouble(T::getWeight).average().getAsDouble();// 平均值
	}

	/**
	 * 并发安全普通随机数对象
	 * 
	 * @return
	 */
	public static Random random() {
		return ThreadLocalRandom.current();
	}

	/**
	 * 加密随机数对象
	 * 
	 * @return
	 */
	public static SecureRandom secureRandom() {
		return new SecureRandom();
	}

	/**
	 * 随机从min~max
	 * 
	 * @param min
	 * @param max
	 * @return
	 */
	public static int random(int min, int max) {
		return random().nextInt(max - min + 1) + min;
	}

	public static boolean nextBoolean() {
		return RandomUtils.nextBoolean();
	}

	public static byte[] nextBytes(int count) {
		return RandomUtils.nextBytes(count);
	}

	public static double nextDouble() {
		return RandomUtils.nextDouble();
	}

	/**
	 * 随机 startInclusive ~ endInclusive
	 * 
	 * @param startInclusive
	 * @param endInclusive
	 * @return
	 */
	public static double nextDouble(double startInclusive, double endInclusive) {
		return RandomUtils.nextDouble(startInclusive, endInclusive);
	}

	public static float nextFloat() {
		return RandomUtils.nextFloat();
	}

	/**
	 * 随机 startInclusive ~ endInclusive
	 * 
	 * @param startInclusive
	 * @param endInclusive
	 * @return
	 */
	public static float nextFloat(float startInclusive, float endInclusive) {
		return RandomUtils.nextFloat(startInclusive, endInclusive);
	}

	public static int nextInt() {
		return RandomUtils.nextInt();
	}

	/**
	 * 从x~2x之间的随机数
	 * 
	 * @param x
	 * @return
	 */
	public static int xTo2x(int x) {
		return random(x, x << 1);
	}

	/**
	 * 随机 startInclusive ~ endExclusive-1
	 * 
	 * @param startInclusive
	 * @param endExclusive
	 * @return
	 */
	public static int nextInt(int startInclusive, int endExclusive) {
		return RandomUtils.nextInt(startInclusive, endExclusive);
	}

	public static long nextLong() {
		return RandomUtils.nextLong();
	}

	/**
	 * 随机 startInclusive ~ endExclusive-1
	 * 
	 * @param startInclusive
	 * @param endExclusive
	 * @return
	 */
	public static long nextLong(long startInclusive, long endExclusive) {
		return RandomUtils.nextLong(startInclusive, endExclusive);
	}

	/**
	 * 随机0~(n-1)
	 * 
	 * @param n
	 * @return
	 */
	public static int random(int n) {
		if (n <= 0)
			throw new RuntimeException("random:(0~" + (n - 1) + ")");
		return random().nextInt(n);
	}

	/**
	 * 按概率从列表中选择一个元素
	 * 
	 * @param list 源数据
	 * @return
	 */
	public static <T extends Probability> T takeOneByProbabolity(Iterable<T> iterable) {
		List<T> list = Lists.newArrayList(iterable);
		return takeOneByProbabolity(getSum(list), list);
	}

	/**
	 * 按概率从列表中选择一个元素
	 * 
	 * @param total 最大值
	 * @param list  源数据
	 * @return
	 */
	public static <T extends Probability> T takeOneByProbabolity(int total, Iterable<T> iterable) {
		int r = random(1, total);
		List<T> tempList = Lists.newArrayList(iterable);
		int s = 0;
		for (T p : tempList) {
			s += p.getWeight();
			if (r <= s)
				return p;
		}
		return null;
	}
	
	/**
	 * 基于权重随机抽取 不重复
	 * @param n 个
	 * @param <T>
	 * @return 
	 * @return
	 */
	public static <T extends Probability> List<T> takeByProbabolityNoSame(int n, Iterable<T> iterable) {
		
		if(n <= 0) {
			return null;
		}
		
		List<T> tempList = Lists.newArrayList(iterable);
		
		int size = tempList.size();
		
		if(tempList.size() <= n) {
			return tempList;
		}
		
		List<T> out = Lists.newArrayList();
		
		for (int i = 0; i < n ; i++) {
			
			int r = random(1, getSum(tempList));
			
			int s = 0;
			
			for (T p : tempList) {
				s += p.getWeight();
				if (r <= s) {
					out.add(p);
					tempList.remove(p);
					break;
				}
			}
		}
		return out;
	}
	
	
	public static void main(String[] args) {
		List<WeightConfig> configs = Lists.newArrayList();
		
		configs.add(WeightConfig.build(1, 10));
		configs.add(WeightConfig.build(2, 20));
		configs.add(WeightConfig.build(3, 30));
		configs.add(WeightConfig.build(4, 40));

		List<WeightConfig> out = takeByProbabolityNoSame(3, configs);
		
		for (WeightConfig config : out) {
			log.info("{} {}",config.getId(),config.getWeight());
		}
	}

	/**
	 * 纯随机，从列表中随机num个元素,不重复
	 * 
	 * @param list
	 * @param num
	 * @return
	 */
	public static <T> List<T> randomByNumber(Iterable<T> iterable, int num) {
		List<T> result = Lists.newArrayListWithCapacity(num);
		List<T> tempList = Lists.newArrayList(iterable);
		while (tempList.size() > 0 && result.size() < num)
			result.add(tempList.remove(random(tempList.size())));
		return result;
	}

	/**
	 * 纯随机，从列表中随机count次，可重复
	 * 
	 * @param list
	 * @param count
	 * @return
	 */
	public static <T> List<T> randomly(Iterable<T> iterable, int count) {
		List<T> result = Lists.newArrayListWithCapacity(count);
		ArrayList<T> tempList = Lists.newArrayList(iterable);
		AtomicInteger c = new AtomicInteger();
		while (c.getAndIncrement() < count)
			result.add(tempList.get(random(tempList.size())));
		return result;
	}

}
